<?
/* SECURITY (don't touch) */
defined('_INCL')||(header('HTTP/1.1 403 Forbidden')&die('403.14 - Access denied.'));
/**************************/


//----------------------------------------
/* Initialize Session-variables */
	$_SESSION['ID'] = array();
	
	$_ENV['Fingerprint'] = explode('.',$_SERVER['REMOTE_ADDR']);
		$_ENV['Fingerprint'] = $_ENV['Fingerprint'][0].$_ENV['Fingerprint'][1];
		$_ENV['Fingerprint'] .= $_SERVER['HTTP_USER_AGENT'];
		$_ENV['Fingerprint'] .= $_SERVER['HTTP_ACCEPT'];
		$_ENV['Fingerprint'] .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
		$_ENV['Fingerprint'] .= substr($_SERVER['HTTP_ACCEPT_ENCODING'],0,4);
		$_ENV['Fingerprint'] .= $_SERVER['HTTP_ACCEPT_CHARSET'];
		$_ENV['Fingerprint'] .= $_SERVER['HTTP_DNT'];
		$_ENV['Fingerprint'] = hash('md4',$_ENV['Fingerprint']);//Length = 32 characters

	$_ENV['CurrTick'] = substr( floor(($_SERVER['REQUEST_TIME']/3600)/_SESSMAX) ,-3 );
	
	$_ENV['SESScookie'] = 'Sess_'.$_ENV['Fingerprint'];
	
	$_ENV['LoopToken'] = 'Loading';
	
	$_ENV['DirWeek'] = $_ENV['URLs']['SessPath'].'y'.date('Y').'-w'.ZeroPad(date('W'),2).'/';
	
	$_ENV['UniversalJS'] .= "var _FP = '".$_ENV['Fingerprint']."';".PHP_EOL;

	$_ENV['Headers'] = '<meta charset="utf-8">'.PHP_EOL;
	$_ENV['Headers'] .= '<meta name="author" content="'.$_ENV['SiteAuthor'].'">'.PHP_EOL;
	$_ENV['Headers'] .= '<meta name="generator" content="'.$_ENV['SiteGenerator'].'">'.PHP_EOL;
	
	$Linkfiles[] = $_ENV['URLs']['RootUrl'].'_Core/Design/favicon.png';
	$Linkfiles[] = $_ENV['URLs']['RootUrl'].'_Core/Design/00_cssReset.css';
	$Linkfiles[] = $_ENV['URLs']['RootUrl'].'_Core/Design/01_Base.css';
	$Linkfiles[] = $_ENV['URLs']['RootUrl'].'_Core/3rdParty/jquery.min.js';
	
/* END Session-variables */



//----------------------------------------
/* Anatomy of a session ...

Alive FileName:
Sess_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123C
Sess_[·········Fingerprint··········][···········Random···········][Tick][C/F]
0    5            (32)                             (32)              (3)  (1)

Outdated FileName:
GC-reason_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123C

Cookie:
[Sess_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123'

*/


//----------------------------------------
/* Define ID */
	function Sess_DefineID(){
		$Cookie = isset($_COOKIE[$_ENV['SESScookie']]);
		
		$Matches = array_diff(scandir($_ENV['URLs']['SessPath'].'_Alive'), array('..', '.','index.php'));
		foreach($Matches as $K => $V){//Traverse all alive sess-files
			if( strlen($V)!==73 || strpos( $V,'_' )===false ){//Sanity
				unset($Matches[$K]);
			} else {
				$State = explode('_',$V);
				$Print = substr($State[1],0,32);
				$Rand = substr($V,-36,32);
				$Tick = substr($V,-4,3);
				$Type = substr($V,-1);
				
				if( $Cookie && $Print===$_ENV['Fingerprint'] && $Rand===substr($_COOKIE[$_ENV['SESScookie']],0,-3) ) {//Full match with cookie
					if( $Type === 'F' ){//Upgrade session-type in file-name
						$V = substr($V,0,-1);
						$Path = $_ENV['URLs']['SessPath'].'_Alive/'.$V;
						rename($Path.'F',$Path.'C');
						$V = $V.'C';
						Logger(__FUNCTION__,'Full cookie alive F-match, upgrading to C');//DEBUG
					} else {
						Logger(__FUNCTION__,'Full cookie alive C-match');//DEBUG
					}
					$_SESSION['ID'][] = $V; return true;//Shortcut exit
				} else if( $Print!==$_ENV['Fingerprint'] || (!$Cookie && $Type !== 'F') || ($Cookie && $Type !== 'C') ){
					unset($Matches[$K]);
				}
			}
		}
		if( isset($_GET[$_ENV['LoopToken']]) ){//We are looping, but havn't found a cookie-match!
			//remove offending cookie, and continue to create a fresh session
			setcookie( $_ENV['SESScookie'],'',time()-100000,'/'.$_ENV['URLs']['MainDir'],$_ENV['URLs']['Domaine'],false,true);
			Logger(__FUNCTION__,'Failed loop');
			goto A;
		} else if( count($Matches) > 0 ) {//One or more F-matches: Open each file, and check full IP and full User-agent
			foreach($Matches as $K => $V){//Traverse remaining sessions
				$Data = unserialize(file_get_contents($_ENV['URLs']['SessPath'].'_Alive/'.$V));
				if( end($Data['Client']['Agent'])===$_SERVER['HTTP_USER_AGENT'] && end($Data['Client']['IP'])===$_SERVER['REMOTE_ADDR']){
					Logger(__FUNCTION__,'Late cookie alive F-match');//DEBUG
					$_SESSION['ID'][] = $V; return true;//Shortcut exit
				}
			}
		}
		
		
	/* No alive matches ... so search for outdated */
		$Matches = array_diff(scandir($_ENV['DirWeek']), array('..', '.'));
		$TimeLimit = $_SERVER['REQUEST_TIME']-(_PING*50);//How far backwards should we look?
		foreach($Matches as $K => $V){//Traverse all outdated sess-files
			if( strlen($V)<70 || strpos( $V,'_' )===false ){//Sanity
				unset($Matches[$K]);
			} else {
				$State = explode('_',$V);
				$Print = substr($State[1],0,32);
				$State = $State[0];
				$Type = substr($V,-1);
				$Rand = substr($V,-36,32);
			
				if( $State !== 'GC-PingAge' || $Print !== $_ENV['Fingerprint'] || filemtime($_ENV['DirWeek'].$V) < $TimeLimit ){
					unset($Matches[$K]);
				} else if( $Cookie && $Rand === $_COOKIE[$_ENV['SESScookie']] ) {//Full match with cookie
					$Path1 = $_ENV['DirWeek'].$V;
					$V = explode('_',$V); $V = 'Sess_'.substr($V[1],-1).'C';
					$Path2 = $_ENV['URLs']['SessPath'].'_Alive/'.$V;
					rename($Path1,$Path2);
					Logger(__FUNCTION__,'Full cookie GC-PingAge C-match, setting alive');
					$_SESSION['ID'][] = $V; return true;//Shortcut exit
				} else if( (!$Cookie && $Type !== 'F') || ($Cookie && $Type !== 'C') ){
					unset($Matches[$K]);
				}
			}
		}
		if( count($Matches) > 0 ) {//One or more F-matches: Open each file, and check full IP and full User-agent
			foreach($Matches as $K => $V){//Traverse remaining sessions
				$Data = unserialize(file_get_contents($_ENV['DirWeek'].$V));
				if( end($Data['Client']['Agent'])===$_SERVER['HTTP_USER_AGENT'] && end($Data['Client']['IP'])===$_SERVER['REMOTE_ADDR']){
					$Path1 = $_ENV['DirWeek'].$V;
					$V = explode('_',$V);
					$V = 'Sess_'.$V[1];
					$Path2 = $_ENV['URLs']['SessPath'].'_Alive/'.$V;
					rename($Path1,$Path2);
					Logger(__FUNCTION__,'Half cookie GC-PingAge F-match, setting alive');
					$_SESSION['ID'][] = $V; return true;//Shortcut exit
				}
			}
		}
		
	/* No outdated matches either ... so create a fresh session */
		A:
		$_SESSION['Sess_Created'] = $_SERVER['REQUEST_TIME'];
		$_SESSION['Sess_Rand'][] = hash('md4',rand(0,99999)._CRYPT1.microtime(true));
		$_SESSION['Client']['IP'][] = $_SERVER['REMOTE_ADDR'];
		$_SESSION['Client']['Agent'][] = $_SERVER['HTTP_USER_AGENT'];
		$_SESSION['ID'][] = $_ENV['SESScookie'].end($_SESSION['Sess_Rand']).$_ENV['CurrTick'].'F';
		Logger(__FUNCTION__,'Create fresh F-session: '.end($_SESSION['ID']));
		Sess_SetCookie();
		Sess_Write();

	/* Reload page */
		if( !isset($_GET[$_ENV['LoopToken']]) ){ Logger(__FUNCTION__,'Looping',true);
			$URL = $_ENV['URLs']['RootUrl'].$_ENV['URLs']['CallFile'].'?'.$_ENV['LoopToken'];
			header('Location: '.$URL, true, 307);//307=Temporary Redirect
			die("<p>Loading ... <a href=\"$URL\">Continue</a></p>");//Will show, only if redirect fails
		} 
		$error = "<b>SessionID kunne ikke fastlægges.</b> Dette burde aldrig ske! ... Please panic now :)<br/>Naaaa, don't panic yet <a href='index.php'>click here</a>!";
		Logger(__FUNCTION__,$error,true);
		die("<p>$error</p>");
		return false;
	}



//----------------------------------------
/* Set or Update session-cookie */
	function Sess_SetCookie(){
		$CookieValue = substr(end($_SESSION['ID']),-36,35);
		if( !empty($_COOKIE[$_ENV['SESScookie']]) && $_COOKIE[$_ENV['SESScookie']] === $CookieValue ){
			Logger(__FUNCTION__,'Not needed');
			return true;
		} else {
			$Result = setcookie( $_ENV['SESScookie'],$CookieValue,0,
				'/'.$_ENV['URLs']['MainDir'],$_ENV['URLs']['Domaine'],false,true
			);
			if($Result === true){//Record the cookie, so we don’t set it again during this request
				$_COOKIE[$_ENV['SESScookie']] = $CookieValue;
				Logger(__FUNCTION__,$_COOKIE[$_ENV['SESScookie']].'=>'.$CookieValue);
				return true;
			} else {
				Logger(__FUNCTION__,'Failed: '.$Result);
				return false;
			}
		}
	}


//----------------------------------------
/* Read a Session-file */
	function Sess_Read( $FileName='' ){ Logger(__FUNCTION__,$FileName);
		$FileName = (empty($FileName)) ? end($_SESSION['ID']) : $FileName;
		$FilePath = $_ENV['URLs']['SessPath'].'_Alive/'.$FileName;
		
		if( !file_exists($FilePath) ){ Logger(__FUNCTION__,'Error: '.$FileName,true);
			die('Error reading Session: '.$FileName);
		}
		
		$Data = unserialize(file_get_contents($_ENV['URLs']['SessPath'].'_Alive/'.$FileName));
		$_SESSION = array_merge($Data,$_SESSION);		
		
		$_SESSION['Sess_Read'][] = $_SERVER['REQUEST_TIME'].' ('.$_ENV['URLs']['CurrFile'].')';
		
		if( isset($_GET[$_ENV['LoopToken']]) ){//Remove load-token from URL
			$URL = $_ENV['URLs']['RootUrl'].$_ENV['URLs']['CallFile'];
			header('Location: '.$URL, true, 307);//307=Temporary Redirect
			die('Loading ...');//Will show, only if redirect fails
		}
		
		return true;
	}



//----------------------------------------
/* Write to session-file */
	function Sess_Write($FileName = ''){ Logger(__FUNCTION__,end($_SESSION['ID']),true);
		$FileName = (empty($FileName)) ? end($_SESSION['ID']) : $FileName;
		
		if( _DEBUG && strlen($FileName)!==73 ){ die('Wrong length of filename to write : '.$FileName); }//DEBUG
		
		$_SESSION['Sess_Write'][] = $_SERVER['REQUEST_TIME'].' ('.$_ENV['URLs']['CurrFile'].')';
		$Data = serialize($_SESSION);
		return file_put_contents( $_ENV['URLs']['SessPath'].'_Alive/'.$FileName,$Data,LOCK_EX );	
	}



//----------------------------------------
/* Garbage-collect session-files */
	function Sess_GC(){
		$AliveSess = array_diff(scandir($_ENV['URLs']['SessPath'].'_Alive/'), array('..', '.','index.php'));
		
		foreach($AliveSess as $File) {
			if( strlen($File)!==73 ){
				Sess_Outdate($File,'GC-Malformed');
			} else if ( filemtime($_ENV['URLs']['SessPath'].'_Alive/'.$File) < $_SERVER['REQUEST_TIME']-(_PING*3) ){//No access for 3 Pings (= Connection lost)
				Sess_Outdate($File,'GC-PingAge');
			} else {
				$TickDist = abs((int)substr($File,-4,3)-$_ENV['CurrTick']);
				$TickDist = min($TickDist, 1000-$TickDist);
				if( $TickDist > 2 ){//This session has been kept alive longer than allowed
					Sess_Outdate($File,'GC-TickAge');
				}
			}
		}
		return true;
	}
	


//----------------------------------------
/* Outdate a session-file */
	function Sess_Outdate($FileName,$OutdateReason='???'){ Logger(__FUNCTION__,$OutdateReason.': '.$FileName);
		$FileName2 = explode('_',$FileName);
		$FileName2 = $OutdateReason.'_'.$FileName2[1];
		return rename($_ENV['URLs']['SessPath'].'_Alive/'.$FileName, $_ENV['DirWeek'].$FileName2);
	}



//--------------------------------
/* Check, update or create Session-filestructure */
	function Sess_FileStructure(){
		if ( !is_dir($_ENV['URLs']['SessPath'].'_Alive') ){//No SessPath! (= either first install or changed _CRYPTs)
			$Scan = array_diff(scandir($_ENV['URLs']['RootPath'].'_Core/Session/'), array('..','.','index.php'));
			foreach( $Scan as $DirName ){//Check for old file-structure
				if( strpos($DirName,'_SessDB') === 0 ){//We found an old SESS-directory
					Logger(__FUNCTION__,'Renaming old structure');
					return rename($_ENV['URLs']['RootPath'].'_Core/Session/'.$DirName, $_ENV['URLs']['SessPath']);
				}
			}
			Logger(__FUNCTION__,'Creating new structure');
			mkdir($_ENV['DirWeek'], 0777, true);//Recursive creation
			return mkdir($_ENV['URLs']['SessPath'].'_Alive', 0777, false);//Non-recursive creation
		} else if( !is_dir($_ENV['DirWeek']) ){
			Logger(__FUNCTION__,'Creating new DirWeek');
			mkdir($_ENV['DirWeek'], 0777, false);//Non-recursive creation
		} else {
			Logger(__FUNCTION__,'Intact');
		}
		return true;
	}





//----------------------------------------------
/** UNIVERSAL FUNCTIONS **/
//------------------------------------------
/* Logging to file */
	function Logger($Function,$Msg='No description',$Write2File=false){
		if(_LOG===0){
			return false;
		} else if(_LOG===1){
			$_ENV['Log'][] = $Function.'('.$Msg.')';
		} else {
			$MicroTime = explode('.',microtime(true));
			$MicroTime = date('s').'.'.str_pad($MicroTime[1],4,'0');
			$_ENV['Log'][] = $Function.'('.$Msg.') @ '.$_ENV['URLs']['CurrFile'].' | Mem:'.ceil(memory_get_usage()/1024).'kb';
		}
		
		if($Write2File){
			$Page = $_ENV['REQUEST_METHOD'].'>'.$_SERVER['PHP_SELF'].$_ENV['REQUEST_URI'];
			$File = $_ENV['URLs']['LogPath'].$_ENV['SiteName'].'_'.date('l').'_'.hash('crc32b',_CRYPT1).'.txt';
			
			$Data = 
				PHP_EOL.PHP_EOL.'----- '.date('H:i:s').'|REQUEST: '.$_SERVER['REQUEST_URI'].' (via '.$_ENV['URLs']['CallFile'].') by:'.end($_SESSION['ID'])
				.PHP_EOL.'('.$_SERVER['REMOTE_ADDR'].') '.$_SERVER['HTTP_USER_AGENT']
				.PHP_EOL
				."\t• ".implode(PHP_EOL."\t• ",$_ENV['Log'])
				.PHP_EOL.'Mem:'.ceil(memory_get_usage()/1024).'kb'
			;
			
			$_ENV['Log'] = array();
			
			if( !file_exists($File) || filemtime($File) < time()-86400 ){//Check if todays file’s last change-date, is actually older than a day.
				return file_put_contents( $File,$Data, LOCK_EX );//Overwrite old data, or create new file
			} else {
				return file_put_contents( $File,$Data, FILE_APPEND | LOCK_EX );//Append current data
			}
		}
		return true;
	}



//------------------------------------------
function FindFiles($Postfix='.css',$StartDir='',$SkipDir='_Core',$AsPath=false){
	$StartDir = $_ENV['URLs']['RootPath'].$StartDir;//Make file-path absolute
	$Postfix = '#'.$Postfix.'$#';//Add delimiters for regex
	$Results = array();
	$Iterator =
		new RecursiveIteratorIterator(
			new RecursiveDirectoryIterator(
				$StartDir,
				FilesystemIterator::SKIP_DOTS |
				FilesystemIterator::KEY_AS_PATHNAME
			),
			RecursiveIteratorIterator::SELF_FIRST,
			RecursiveIteratorIterator::LEAVES_ONLY
		);
	foreach($Iterator as $FilePath => $FileObj){
		if( !$FileObj->isFile() || strpos($FilePath,$SkipDir) ){
			continue;
		} else if( preg_match($Postfix,$FileObj->getFileName()) ){
			$Results[] = $FilePath;
		}
	}
	
	if(!$AsPath){
		foreach($Results as &$Entry){
			$Entry = str_replace($_ENV['URLs']['RootPath'],$_ENV['URLs']['RootUrl'],$Entry);
		}
	}
	//die(var_dump($Results));//DEBUG
	return $Results;
}



//------------------------------------------
/* Encryption. (Adapted from functions by: Anonymous¶, 2012) */
	function CryptA( $decrypted, $password, $salt='What•ever?¡^º' ){ if(_DEBUG) return $decrypted;//DEBUG
		
		$key = hash('SHA256', $salt.$password, true);// Build a 256-bit $key
		srand();
		$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
		if(strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) return false;
		$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $decrypted.md5($decrypted), MCRYPT_MODE_CBC, $iv));
		return strrev($iv_base64.$encrypted);
	}
	function CryptB( $encrypted, $password, $salt='What•ever?¡^º' ){ if(_DEBUG) return $encrypted;//DEBUG
	
		$key = hash('SHA256', $salt.$password, true);
		$iv = base64_decode(substr(strrev($encrypted), 0, 22).'==');
		$encrypted = substr($encrypted, 22);// Remove $iv from $encrypted.
		$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0\4");
		$hash = substr($decrypted, -32);
		$decrypted = substr($decrypted, 0, -32);// Remove the last 32 characters from $decrypted.
		if (md5($decrypted) != $hash) return false;
		return $decrypted;
	}



//------------------------------------------
function doCURL( $URL='', $Gets=array(), $Posts=array(), $Cookies=array() ){
	$Gets = array_merge($_GET,$Gets); $Posts = array_merge($_POST,$Posts);
	$URL = $_ENV['URLs']['RootUrl'].$URL.'?'.http_build_query($Gets);
	
	$COOKIEstr = '';
	if(empty($Cookies) && !empty($_COOKIE)){
		foreach($_COOKIE as $K=>$V){
			$COOKIEstr .= $K.'='.$V.'; ';
		}
	} else if(!empty($Cookies)) {
		foreach($Cookies as $K=>$V){
			$COOKIEstr .= $K.'='.$V.'; ';
		}
	}
	
	$Handle = curl_init($URL);
	$CURLoptions = array(//Copy most of the options from the original client-request
		CURLOPT_HEADER => 0,
		CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'],
		CURLOPT_HTTPHEADER => array(
			'Accept-Language: '.$_SERVER['HTTP_ACCEPT_LANGUAGE'],
			'Accept-Charset: '.$_SERVER['HTTP_ACCEPT_CHARSET'],
			'Accept: '.$_SERVER['HTTP_ACCEPT'],
			'DNT: '.$_SERVER['HTTP_DNT'],
			'REMOTE_ADDR: '.$_SERVER['REMOTE_ADDR'],
			'X_REFERER: '.$_SERVER['PHP_SELF'],
		),
		CURLOPT_ENCODING => $_SERVER['HTTP_ACCEPT_ENCODING'],
		CURLOPT_POST => 1, CURLOPT_POSTFIELDS => http_build_query($Posts),
		CURLOPT_COOKIE => $COOKIEstr, CURLOPT_COOKIESESSION => 1,
		
		CURLOPT_FRESH_CONNECT => 1, CURLOPT_FORBID_REUSE => 1,
		CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_TIMEOUT => 5,
		
		CURLOPT_RETURNTRANSFER => 1,
		CURLOPT_VERBOSE => 1,
	); curl_setopt_array($Handle,$CURLoptions);
	
	if( !$Returned=curl_exec($Handle)){
		trigger_error(curl_error($Handle));
	} 
		//$Info = print_r(curl_getinfo($Handle));//DEBUG
	curl_close($Handle);
		//return $Returned.'<pre>INFO:'.$Info.'</pre>';//DEBUG
	return $Returned;
}


//------------------------------------------
function ZeroPad($Input,$Length) {
	return str_pad($Input,$Length,'0',STR_PAD_LEFT);
} /* JavaScript equivalent */ $_ENV['UniversalJS'] .= "
function ZeroPad(Input,Length){
	while(Input.toString().length<Length){Input='0'+Input;}
	return Input;
}";/* END JavaScript */
	
?>